/**
 * A logic class to perform business logic for Return Module
 */
package rentReturnLogic;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.DecimalFormat;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import paymentEntity.CashEntity;
import paymentEntity.CreditCardEntity;
import paymentService.PaymentService;
import rentReturnEntity.AdditionalEquipment;
import rentReturnEntity.RentEntity;
import rentReturnEntity.ReturnCostEntity;
import rentReturnEntity.ReturnEntity;
import rentReturnService.ReturnService;
import reservationEntity.ReservationEntity;
import reservationService.ReservationService;
import userEntity.MembershipEntity;
import userService.CustomerService;
import userService.MembershipService;
import validator.Validator;
import vehicleEntity.VehicleRentCostEntity;
import vehicleService.SetRateService;


/**
 * @author Yokesh Kumar
 * 
 */
public class ReturnLogic {
	private static ReturnService returnservice = null;
	private static RentEntity rentEntity = null;
	private static VehicleRentCostEntity vehicleRentCost = null;
	private static ReservationEntity reservationDetails = null;
	private static CreditCardEntity creditCardDetails = null;
	private static int updatedMemberPoints = 0;
	private static int pointsRedeemed = 0;
	private static int membershipPointforPayment = 0;

	/**
	 * A method to search if Return for a particular rent ID is done or not
	 * @PRE called from searchRent controller
	 * @POST Controllers dispatches action based on the returned value
	 * @param returnId
	 * @return boolean
	 * @throws SQLException
	 */
	public boolean searchReturn(int returnId) throws SQLException{
		boolean status = false;
		try{
			returnservice = new ReturnService();
			status =returnservice.searchReturn(returnId);
		}catch(SQLException sqlException){
			sqlException.printStackTrace();
			returnservice.closeConnection();
			throw sqlException;
		}
		returnservice.closeConnection();
		System.out.println("search return connection closed");
		return status;
	}

	/**
	 * This method searches for the rent details of the given rent ID
	 * @PRE This method is called from searchRent Controller only if return for the given Rent ID is False
	 * @POST  searchRent Controller loads the ReturnSecondScreenFXML 
	 * @param rentId
	 * @return rentEntity
	 * @throws SQLException 
	 */

	public RentEntity searchRentWithRentid(int rentId) throws SQLException {
		try {
			returnservice = new ReturnService();
			ResultSet rentalDetails = returnservice.searchRentWithRentid(rentId);
			rentEntity = null;
			if (rentalDetails != null && rentalDetails.next()) {
				rentEntity = new RentEntity();
				rentEntity.setRentDate(LocalDate.parse(rentalDetails.getDate("rentDate").toString()));
				rentEntity.setRentTime(rentalDetails.getTime("rentTime").toString());
				rentEntity.setOdometerReading(rentalDetails.getInt("odometerReading"));
				rentEntity.setReserveId(rentalDetails.getInt("reserveId"));
				rentEntity.setVehicleId(rentalDetails.getInt("vehicleId"));
				rentEntity.setRentId(rentalDetails.getInt("rentId"));
			} else {
				//System.out.println("No values");
			}
		} catch (SQLException e) {
			e.printStackTrace();
			returnservice.closeConnection();
			throw e;
		}
		returnservice.closeConnection();
		System.out.println("search with renID DB Connection closed");
		return rentEntity;
	}

	/**
	 * This method searches for the rent details of the given vehicle ID
	 * @PRE This method is called from searchRent Controller only if VehicleID status is rented in rental_vehicle table
	 * @POST  searchRent Controller loads the ReturnSecondScreenFXML  
	 * @param rentId
	 * @return rentEntity
	 * @throws SQLException 
	 */
	public RentEntity searchRentWithVehicleid(int vehicleID) throws SQLException {
		try {
			returnservice = new ReturnService();
			ResultSet rentalDetails = returnservice.searchRentWithVehicleid(vehicleID);
			ResultSet rentalVehicleDetails = null;
			if (rentalDetails != null && rentalDetails.next()) {
				//System.out.println("rental details "+rentalDetails.getInt("rentId"));
				int rentID = rentalDetails.getInt("rentId");
				rentalVehicleDetails = returnservice.searchRentWithRentid(rentID);
				if(rentalVehicleDetails!= null && rentalVehicleDetails.next()){
					rentEntity = new RentEntity();
					rentEntity.setRentDate(LocalDate.parse(rentalVehicleDetails.getDate("rentDate").toString()));
					rentEntity.setRentTime(rentalVehicleDetails.getTime("rentTime").toString());
					rentEntity.setOdometerReading(rentalVehicleDetails.getInt("odometerReading"));
					rentEntity.setRentId(rentalVehicleDetails.getInt("rentId"));
					rentEntity.setVehicleId(rentalVehicleDetails.getInt("vehicleId"));
					rentEntity.setReserveId(rentalVehicleDetails.getInt("reserveId"));
				}
			} else {
				rentEntity = null;
				//System.out.println("No values");
			}
		} catch (SQLException e) {
			e.printStackTrace();
			returnservice.closeConnection();
			throw e;
		}
		returnservice.closeConnection();
		System.out.println("search rent with vehcle ID DB Connection closed");
		return rentEntity;
	}

	/**
	 This method calculates the final bill value based on the input provided in ReturnSecondScreenFXML screen
	 * @PRE invoked from generateBill controller
	 * @POST  generateBill controller loads the ReturnPriceBreakUpScreenFXML
	 * @param returnEntity
	 * @param membershipDetails
	 * @param pointsPerDay
	 * @return ReturnCostEntity
	 * @throws SQLException
	 */

	public ReturnCostEntity calculateBill(ReturnEntity returnEntity,MembershipEntity membershipDetails,int pointsPerDay) throws SQLException {

		Double weeklyRentalCost=0.0,hourlyRentalCost=0.0,dailyRentalCost=0.0,addEquipDaily = 0.0,addEquipHourly = 0.0;
		Double weeklyInsCost=0.0,hourlyInsCost=0.0,dailyInsCost=0.0;
		Double dedInsCost = 0.00;
		double addEquipCost = 0.00;
		int hours = 0,kmLimit=0;
		double datePenalty = 0.0;
		double kmExceededFine = 0.00;
		double fuelCharges = 0.00;
		double serviceFees = 0.00;
		double ageFees = 0.00;
		double taxPercentage = 0.00;
		double damageCost = 0.00;
		String datePenaltyDesc = null;
		DecimalFormat decimalFormatter = new DecimalFormat("#0.00");
		SetRateService fetchRateService = new SetRateService();
		ReservationService reservationService = null; 
		ResultSet resultSet =  null;
		try {
			resultSet =  fetchRateService.fetchFuelRate();
			if(resultSet!=null && resultSet.next()){
				returnEntity.setCurrentFuelRate(resultSet.getDouble("fuelRate"));
			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			fetchRateService.closeConnection();
			throw e;
		}
		fetchRateService.closeConnection();	
		System.out.println("fethc rate service connection closed");
		resultSet = null;

		returnservice = new ReturnService();
		ArrayList<Double> feesVariables =  returnservice.fetchFeesVariables();
		serviceFees = feesVariables.get(0);
		if(reservationDetails.getDriverAge().equalsIgnoreCase("between 21 and 24")){
			ageFees = feesVariables.get(1);	   
		}else{
			ageFees = 0;
		}

		taxPercentage = feesVariables.get(2);
		returnservice.closeConnection();
		System.out.println("fetch fees variable service closed ");
		ResultSet addEquipRate = null;
		reservationService = new ReservationService();
		resultSet = reservationService.fetchAddEquip(reservationDetails.getReservationID());		
		List<AdditionalEquipment> addEquipList = new ArrayList<AdditionalEquipment>();
		AdditionalEquipment reservedAdditionalEquipment = null;
		try {
			if((resultSet!=null) && (resultSet.next())){
				resultSet.beforeFirst();
				try {
					while(resultSet.next()){
						reservedAdditionalEquipment =new AdditionalEquipment();
						reservedAdditionalEquipment.setAdditionalEquipmentName(resultSet.getString("equipmentName").trim());
						ArrayList<String> equipList = new ArrayList<String>();
						equipList.add(reservedAdditionalEquipment.getAdditionalEquipmentName());
						addEquipRate = reservationService.fetchAddEquipRate(equipList);
						if(addEquipRate.next()){				
							reservedAdditionalEquipment.setDailyRate(addEquipRate.getDouble("dailyRate"));
							reservedAdditionalEquipment.setHourlyRate(addEquipRate.getDouble("hourlyRate"));
						}
						addEquipList.add(reservedAdditionalEquipment);
						returnEntity.setEquipList(addEquipList);
					}
				} catch (SQLException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
					reservationService.closeConnection();
					throw e;
				}
			}else{
				System.out.println("No add equip");
			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			reservationService.closeConnection();
			throw e;
		}
		reservationService.closeConnection();
		System.out.println("add equip service db cnnection closed ");
		if(addEquipList.size() > 0){
			for(AdditionalEquipment equip : addEquipList){
				addEquipDaily = addEquipDaily+equip.getDailyRate();
				addEquipHourly = addEquipHourly+ equip.getHourlyRate();
			}
		}
		fuelCharges = returnEntity.getFuelReading() * returnEntity.getCurrentFuelRate();
		returnEntity.setFuelCharges(fuelCharges);
		weeklyRentalCost = vehicleRentCost.getWeeklyRate();
		hourlyRentalCost = vehicleRentCost.getHourlyRate();
		dailyRentalCost = vehicleRentCost.getDailyRate();
		weeklyInsCost = vehicleRentCost.getWeeklyInsRate();
		hourlyInsCost = vehicleRentCost.getHourlyInsRate();
		dailyInsCost = vehicleRentCost.getDailyInsRate();
		kmLimit = vehicleRentCost.getKmLimit();
		LocalDate rentDate = returnEntity.getRentedDate();
		LocalDate actualReturnDate = returnEntity.getReturnDate();
		LocalDate projectedReturnDate = reservationDetails.getReturnDate();
		if(actualReturnDate.isAfter(projectedReturnDate)){
			datePenalty = (actualReturnDate.getDayOfYear() -projectedReturnDate.getDayOfYear())*10;
			datePenaltyDesc = "Date Penalty";
		}
		returnEntity.setMiscCharges(datePenalty);
		returnEntity.setMiscDesc(datePenaltyDesc);
		String [] rentTime = returnEntity.getRentedTime().split(":");
		String [] returnTime = returnEntity.getReturnTime().split(":");
		int weeks = (actualReturnDate.getDayOfYear() - rentDate.getDayOfYear())/7;
		int days = (actualReturnDate.getDayOfYear() - rentDate.getDayOfYear())%7;
		System.out.println("weeks b4 check "+weeks);
		System.out.println("days b4 check "+days);
		int rentHH = Integer.parseInt(rentTime[0]);
		int returnHH = Integer.parseInt(returnTime[0]);
		System.out.println("rent HH"+rentHH);
		System.out.println("return hh"+returnHH);
		if(rentHH  > returnHH){
			if(days == 0){
				weeks = weeks-1;
				days = 6;
			}else{
				days=days-1;
			}hours = (24-rentHH)+ returnHH;
		}else if(rentHH <=  returnHH){
			hours = returnHH -  rentHH;
		}
		if(weeks < 0 || days < 0 || hours <0){
			weeks = 0;
			days = 0;
			hours = 0;
		}
		addEquipCost = (((weeks*7) + days) * addEquipDaily) + (hours*addEquipHourly);
		System.out.println("Weeks"+weeks);
		System.out.println("days"+days);
		System.out.println("addequip daily"+addEquipDaily);
		System.out.println( "(weeks*7) + days)"+ (weeks*7) + days );
		System.out.println( "((weeks*7) + days) * addEquipDaily)" + ((weeks*7) + days) * addEquipDaily );
		System.out.println("Hours"+hours);
		System.out.println("addEquipHourly"+addEquipHourly);
		System.out.println("(hours*addEquipHourly)"+(hours*addEquipHourly));
		System.out.println("add euip cost"+addEquipCost);

		returnEntity.setAdditionalEquipmentCost(addEquipCost);
		int daysToRedeem = returnEntity.getDaysToRedeem();
		Double rentalCost=(((weeks * weeklyRentalCost) + (days * dailyRentalCost) + (hours * hourlyRentalCost)));

		returnEntity.setRentalCost(rentalCost);
		Double insuranceCost=(weeks * weeklyInsCost) + (days * dailyInsCost) + (hours * hourlyInsCost);

		returnEntity.setInsCost(insuranceCost);
		int kmTravelled =(returnEntity.getReturnOdometerReading()) - (returnEntity.getRentOdometerReading()) ; 
		int kmLimitforRent = ((weeks*7) + days)*kmLimit;
		System.out.println("Kmlimit " + kmLimit);
		System.out.println("KmlimitForRent " + kmLimitforRent);

		if( kmTravelled > kmLimitforRent){
			System.out.println("Kmtravelled " + kmTravelled);
			kmExceededFine = (kmTravelled - (kmLimitforRent)) * (vehicleRentCost.getKmRate());
			returnEntity.setFine(kmExceededFine);
		} 
		if(kmLimitforRent <= 0){
			kmExceededFine=0.00;
		}
		returnEntity.setFine(kmExceededFine);

		if(returnEntity.isRoadstar()){
			dedInsCost = insuranceCost - (insuranceCost * (0.5));
		}else{
			dedInsCost = 0.00;	
		}
		returnEntity.setDedInsCost(dedInsCost);
		damageCost = returnEntity.getDamageCost();
		Double totalCost = Double.parseDouble(decimalFormatter.format(rentalCost+insuranceCost+addEquipCost+datePenalty+kmExceededFine+fuelCharges + damageCost));
		Double finalCost = Double.parseDouble(decimalFormatter.format(totalCost - (daysToRedeem * dailyRentalCost) - (dedInsCost)));
		double tax = Double.parseDouble(decimalFormatter.format(finalCost*(taxPercentage/100)));
		finalCost = Double.parseDouble(decimalFormatter.format(finalCost+tax+serviceFees+ageFees));
		returnEntity.setTax(tax);
		System.out.println("Final Cost : "+finalCost+"Tax : "+tax+"Service Fees"+serviceFees+"age fees"+ageFees);
		System.out.println("final inc tax"+finalCost);
		membershipPointforPayment = (totalCost.intValue()) / 5;
		pointsRedeemed = pointsPerDay*daysToRedeem;
		updatedMemberPoints = membershipDetails.getPoints() - pointsRedeemed + membershipPointforPayment;
		returnEntity.setTotalCost(Double.parseDouble(decimalFormatter.format(totalCost)));
		returnEntity.setFinalCost(Validator.formatDoubleWithRound(finalCost));
		creditCardDetails.setPaymentAmount(Double.parseDouble( decimalFormatter.format(finalCost)));
		return returnEntity;
	}

	/**
	 * This method updates MemberPoints for an active Super Rent Club Member for every rental payment
	 * @PRE invoked from pay controller, uses the values from calculateBill method
	 * @POST member points are updated for the corresponding user
	 * @param member
	 * @throws SQLException
	 */

	public void updateMemberPoints(MembershipEntity member) throws SQLException {
		MembershipService ms = new MembershipService();
		try{
			ms.updateMemberPoints(updatedMemberPoints, member.getMemberShipId());
			ms.updatePointsRedeemed(member.getMemberShipId(), rentEntity.getRentId(),pointsRedeemed,membershipPointforPayment);
			ms.transactionCommit();
		}catch(SQLException e){
			e.printStackTrace();
			ms.closeConnection();
			throw e;
		}
		ms.closeConnection();
	}

	/**
	 * This method handles credit card payment
	 * @PRE Invoked from pay controller, uses value from calculateBill method
	 * @POST Credit Card payment is handled
	 * @return creditCardDetails
	 * @throws SQLException
	 */
	public CreditCardEntity paybyCreditCard() throws SQLException{
		PaymentService payByCreditCard = new PaymentService();
		ResultSet paymentInfo = null;
		int paymentId = 0;
		try {
			paymentInfo = payByCreditCard.insertIntoPayment(creditCardDetails);
			if(paymentInfo != null && paymentInfo.next()){
				paymentId =  paymentInfo.getInt("last_id");
				creditCardDetails.setPaymentId(paymentId);
			}
			payByCreditCard.insertIntoCreditCard(creditCardDetails);
			payByCreditCard.transactionCommit();


		} catch (SQLException e) {
			try {
				payByCreditCard.transactionRollBack();
			} catch (SQLException e1) {
				e1.printStackTrace();
				payByCreditCard.closeConnection();
				throw e1;
			}
			e.printStackTrace();
			payByCreditCard.closeConnection();
			throw e;
		}
		payByCreditCard.closeConnection();
		System.out.println("pay by cc db connection closed");
		return creditCardDetails;
	}


	/**
	 * This method calls the cash payment method of payment manager for cash
	 * payment returns paymentid
	 * @PRE Invoked from pay controller, uses values from calculateBill method
	 * @POST Cashpayment is handled
	 * @param cash
	 * @return paymentId
	 * @throws SQLException 
	 */

	public int payCashReturn(CashEntity cash) throws SQLException {
		ResultSet cashPaymentInfo = null;
		int paymentId = 0;
		PaymentService cashPayment = new PaymentService();

		try {
			cashPaymentInfo = cashPayment.insertIntoPayment(cash);
			if(cashPaymentInfo!=null && cashPaymentInfo.next()){
				paymentId = cashPaymentInfo.getInt("last_id");
				cash.setPaymentId(paymentId);
				cashPayment.makeCashPayment(cash);

			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			cashPayment.closeConnection();
			throw e;
		}

		cashPayment.closeConnection();
		System.out.println("Cash connection closed");
		return paymentId;
	}

	/**
	 * This method saves the return values
	 * @PRE Invoked from pay controller 
	 * @POST Rented Vehicle is stored and corresponding costs are stored in cost table
	 * @param returnEntity
	 * @return
	 * @throws SQLException 
	 */

	public void savereturn (ReturnEntity returnEntity) throws SQLException {

		returnservice = new ReturnService();
		try {
			returnservice.savereturn(returnEntity);
			returnservice.savereturncosts(returnEntity);
			returnservice.transactionCommit();

		} catch (SQLException e) {
			try {
				returnservice.transactionRollBack();
			} catch (SQLException e1) {
				// TODO Auto-generated catch block

				e1.printStackTrace();
				returnservice.closeConnection();
				throw e1;
			}
			e.printStackTrace();
			returnservice.closeConnection();
			throw e;
		}
		returnservice.closeConnection();
		System.out.println("return service closed");
	}
	/**
	 * This method fetches Reservation details for a given reservation ID
	 * @PRE vehicle must be rented and must not be returned, invoked from initialize method in controller
	 * @POST Vehicle details are set in Vehicle entity, credit card info is set in credit card entity, reservation details are set in reservation entity
	 * @param reservationId
	 * @return reservationDetails
	 * @throws SQLException
	 */
	public ReservationEntity fetchReservationDetails(int reservationId) throws SQLException{
		ReservationService reservationService = new ReservationService();
		creditCardDetails = new CreditCardEntity();
		ResultSet reservationResultSet = null;
		ResultSet vehicleClassRate = null;
		try {
			reservationResultSet = reservationService.fetchReservationForReturn(reservationId);
			if(reservationResultSet!=null && reservationResultSet.next() ){
				reservationDetails= new ReservationEntity();
				reservationDetails.setReservationID(reservationResultSet.getInt("reservationId"));
				reservationDetails.setPhoneNumber(reservationResultSet.getString("customerPhoneNumber"));
				reservationDetails.setRentPackage(reservationResultSet.getString("rentPackage"));
				reservationDetails.setVehicleClass(reservationResultSet.getString("vehicleClass"));
				reservationDetails.setVehicleType(reservationResultSet.getString("VehicleType"));
				reservationDetails.setStatus(reservationResultSet.getString("status"));
				reservationDetails.setReturnDate(LocalDate.parse(reservationResultSet.getDate("returnDate").toString()));
				reservationDetails.setReturnTime(reservationResultSet.getString("returnTime"));
				reservationDetails.setDriverAge(reservationResultSet.getString("age"));
				Date reservation_ReturnDate = reservationResultSet.getTimestamp("returnDate");
				LocalDate reservationReturnDate = (reservation_ReturnDate).toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); 
				reservationDetails.setReturnDate(reservationReturnDate);
				String reservationReturnTime = reservationResultSet.getTime("returnTime").toString();
				reservationDetails.setReturnTime(reservationReturnTime.substring(0,reservationReturnTime.length() -3 ));
				creditCardDetails.setCardNo(reservationResultSet.getString("creditCardNumber"));
				creditCardDetails.setExpiryDate(reservationResultSet.getInt("expiryDate"));
				creditCardDetails.setPaidFor("Vehicle Return");
				creditCardDetails.setPaymentType("Credit Card");
				creditCardDetails.setCardType("Master Card");
			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			reservationService.closeConnection();
			throw e;
		}

		if(reservationDetails != null){

			try {
				vehicleClassRate = reservationService.fetchVehicleClassRate(reservationDetails.getVehicleType());
				vehicleRentCost = new VehicleRentCostEntity();
				if(vehicleClassRate != null && vehicleClassRate.next()){
					vehicleRentCost.setWeeklyRate(vehicleClassRate.getDouble("weeklyRate"));
					vehicleRentCost.setDailyRate(vehicleClassRate.getDouble("dailyRate"));
					vehicleRentCost.setHourlyRate(vehicleClassRate.getDouble("hourlyRate"));
					vehicleRentCost.setWeeklyInsRate(vehicleClassRate.getDouble("weeklyInsCost"));
					vehicleRentCost.setDailyInsRate(vehicleClassRate.getDouble("dailyInsRate"));
					vehicleRentCost.setHourlyInsRate(vehicleClassRate.getDouble("hourlyInsRate"));
					vehicleRentCost.setKmRate(vehicleClassRate.getDouble("kmRate"));
					vehicleRentCost.setKmLimit(vehicleClassRate.getInt("kmLimit"));
				}else{
					System.out.println("No reservation  rec found");
				}
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				reservationService.closeConnection();
				throw e;
			}
		}
		reservationService.closeConnection();
		System.out.println("rese service close connection");
		return reservationDetails;

	}

	/**
	 * This method fetches the membership details, uses the value from fetchReservationDetails method
	 * @PRE Customer must be a Super Rent member, invoked from initialize method in controller
	 * @POST If the member is active, the corresponding values are set
	 * @param phoneNumber
	 * @return member
	 * @throws SQLException
	 */
	public MembershipEntity getMembershipDetails(String phoneNumber) throws SQLException{
		CustomerService customerService = new CustomerService();
		MembershipEntity member = null;
		ResultSet resultSet = null;
		try {
			resultSet = customerService.fetchMembershipDetails(phoneNumber);
			member = new MembershipEntity();
			if(resultSet !=null & resultSet.next()){
				if(resultSet.getString("status").equalsIgnoreCase("active")){
					member.setMemberShipId(resultSet.getInt("membershipId"));
					member.setPoints(resultSet.getInt("points"));
					member.setStatus("active");
				}else{
					member.setStatus("inactive");
					System.out.println("Not an active member");
				}
			}else{
				member.setPoints(0);
				member.setStatus("inactive");
				System.out.println("not a member");
			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			customerService.closeConnection();
			throw e;
		}
		customerService.closeConnection();
		System.out.println("get mem details connection closed");
		return member;
	}
	/**
	 * This method updates the Rented Vehicle to available
	 * @PRE invoked from pay controller
	 * @POST rented vehicle is status is made available
	 * @param vehicleId
	 * @throws SQLException
	 */

	public void updateRentedVehicle(int vehicleId) throws SQLException {
		// TODO Auto-generated method stub
		try{
			returnservice = new ReturnService();
			returnservice.updateRentedVehicle(vehicleId);
		}catch(SQLException e){
			e.printStackTrace();
			returnservice.closeConnection();
			throw e;
		}
		returnservice.closeConnection();
		System.out.println("update rented vehicle connection closed");
	}
}
